// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS.utils;

import java.util.Arrays;
import java.security.*;

/**
 * An implementation of the HMAC message authentication code.
 * 
 * @author Brian Wellington
 */

public class HMAC {

	private MessageDigest digest;
	private int blockLength;

	private byte[] ipad, opad;

	private static final byte IPAD = 0x36;
	private static final byte OPAD = 0x5c;

	private void init(byte[] key) {
		int i;

		if (key.length > blockLength) {
			key = digest.digest(key);
			digest.reset();
		}
		ipad = new byte[blockLength];
		opad = new byte[blockLength];
		for (i = 0; i < key.length; i++) {
			ipad[i] = (byte) (key[i] ^ IPAD);
			opad[i] = (byte) (key[i] ^ OPAD);
		}
		for (; i < blockLength; i++) {
			ipad[i] = IPAD;
			opad[i] = OPAD;
		}
		digest.update(ipad);
	}

	/**
	 * Creates a new HMAC instance
	 * 
	 * @param digest
	 *            The message digest object.
	 * @param blockLength
	 *            The block length of the message digest.
	 * @param key
	 *            The secret key
	 */
	public HMAC(MessageDigest digest, int blockLength, byte[] key) {
		digest.reset();
		this.digest = digest;
		this.blockLength = blockLength;
		init(key);
	}

	/**
	 * Creates a new HMAC instance
	 * 
	 * @param digestName
	 *            The name of the message digest function.
	 * @param blockLength
	 *            The block length of the message digest.
	 * @param key
	 *            The secret key.
	 */
	public HMAC(String digestName, int blockLength, byte[] key) {
		try {
			digest = MessageDigest.getInstance(digestName);
		} catch (NoSuchAlgorithmException e) {
			throw new IllegalArgumentException("unknown digest algorithm "
					+ digestName);
		}
		this.blockLength = blockLength;
		init(key);
	}

	/**
	 * Creates a new HMAC instance
	 * 
	 * @param digest
	 *            The message digest object.
	 * @param key
	 *            The secret key
	 * @deprecated won't work with digests using a padding length other than 64;
	 *             use {@code HMAC(MessageDigest digest, int blockLength,
	 *             byte [] key)} instead.
	 * @see HMAC#HMAC(MessageDigest digest, int blockLength, byte [] key)
	 */
	public HMAC(MessageDigest digest, byte[] key) {
		this(digest, 64, key);
	}

	/**
	 * Creates a new HMAC instance
	 * 
	 * @param digestName
	 *            The name of the message digest function.
	 * @param key
	 *            The secret key.
	 * @deprecated won't work with digests using a padding length other than 64;
	 *             use
	 *             {@code HMAC(String digestName, int blockLength, byte [] key)}
	 *             instead
	 * @see HMAC#HMAC(String digestName, int blockLength, byte [] key)
	 */
	public HMAC(String digestName, byte[] key) {
		this(digestName, 64, key);
	}

	/**
	 * Adds data to the current hash
	 * 
	 * @param b
	 *            The data
	 * @param offset
	 *            The index at which to start adding to the hash
	 * @param length
	 *            The number of bytes to hash
	 */
	public void update(byte[] b, int offset, int length) {
		digest.update(b, offset, length);
	}

	/**
	 * Adds data to the current hash
	 * 
	 * @param b
	 *            The data
	 */
	public void update(byte[] b) {
		digest.update(b);
	}

	/**
	 * Signs the data (computes the secure hash)
	 * 
	 * @return An array with the signature
	 */
	public byte[] sign() {
		byte[] output = digest.digest();
		digest.reset();
		digest.update(opad);
		return digest.digest(output);
	}

	/**
	 * Verifies the data (computes the secure hash and compares it to the input)
	 * 
	 * @param signature
	 *            The signature to compare against
	 * @return true if the signature matches, false otherwise
	 */
	public boolean verify(byte[] signature) {
		return verify(signature, false);
	}

	/**
	 * Verifies the data (computes the secure hash and compares it to the input)
	 * 
	 * @param signature
	 *            The signature to compare against
	 * @param truncation_ok
	 *            If true, the signature may be truncated; only the number of
	 *            bytes in the provided signature are compared.
	 * @return true if the signature matches, false otherwise
	 */
	public boolean verify(byte[] signature, boolean truncation_ok) {
		byte[] expected = sign();
		if (truncation_ok && signature.length < expected.length) {
			byte[] truncated = new byte[signature.length];
			System.arraycopy(expected, 0, truncated, 0, truncated.length);
			expected = truncated;
		}
		return Arrays.equals(signature, expected);
	}

	/**
	 * Resets the HMAC object for further use
	 */
	public void clear() {
		digest.reset();
		digest.update(ipad);
	}

	/**
	 * Returns the length of the digest.
	 */
	public int digestLength() {
		return digest.getDigestLength();
	}

}
